import { reactive, ref } from 'vue';
import { mergeChunk, checkFile, uploadFile } from '@/api/upload';

const chunkSize = 1 * 1024 * 1024; // 切片大小 1 * 1024 * 1024 刚好1M
const maxRequest = ref(6); // 请求最大并发数
export const uploadFileList = ref([]); // 上传文件列表

export const hanldeUploadFile = async (files: any) => {
  let inTaskArrItem = reactive({
    id: files.uid,
    state: 0, // 0不处理，1正在解析，2正在上传，3暂停，4上传完成，5上传中断，6上传失败
    fileHash: '', // 文件hash
    fileName: files.name, // 文件名
    fileSize: files.size, // 文件大小
    allChunkList: [], // 文件切片列表
    whileRequests: [], // 当前正在请求的切片列表
    finishNumber: 0, // 已上传切片数量
    errNumber: 0, // 上传失败数量
    percentage: 0, // 上传进度
    cancal: null, // 取消上传
    url: files.url,
  });
  uploadFileList.value.push(inTaskArrItem);
  // 文件上传
  inTaskArrItem.state = 1; // 0不处理，1正在解析，2正在上传，3暂停，4上传完成，5上传中断，6上传失败
  // 1.解析文件
  if (inTaskArrItem.fileSize === 0) {
    inTaskArrItem.state = 6; // 上传失败
    pauseUpload(inTaskArrItem, false); // 取消上传
  }
  // 2.计算文件hash
  const fileData: any = await useWorker(inTaskArrItem);
  // 3.上传文件
  let baseName = '';
  // 查找文件是否在最后一次切片中
  const lastIndex = inTaskArrItem.fileName.indexOf('.');
  baseName = inTaskArrItem.fileName.slice(0, lastIndex); // 有后缀名
  if (lastIndex == -1) baseName = inTaskArrItem.fileName; // 没有后缀名
  // 文件不同但是内容相同，则视为同一文件
  inTaskArrItem.fileHash = fileData.fileHash + baseName;
  inTaskArrItem.state == 2;
  // 上传文件先验证文件是否已经上传过
  try {
    const res = await checkFile({
      fileHash: inTaskArrItem.fileHash,
      fileName: inTaskArrItem.fileName,
    });
    if (res.status === 200) {
      const { shouldUpload, uploadedList } = res.data;
      if (!shouldUpload) {
        finishTask(inTaskArrItem); // 上传完成

        return false;
      }
      inTaskArrItem.allChunkList = fileData.fileChunkList.map((_item: any, _index: number) => {
        return {
          fileHash: fileData.fileHash + baseName, // 文件hash
          fileSize: files.size, // 文件大小
          fileName: files.name, // 文件名
          chunkIndex: _index, // 切片索引
          chunkFile: _item.chunkFile, // 切片文件
          chunkHash: fileData.fileHash + '-' + _index, // 单个文件切片hash
          chunkSize: chunkSize, // 切片大小
          chunkNumber: fileData.fileChunkList.length, // 切片数量
          finish: false, // 是否上传完成
        };
      });

      // 如果存在已上传的切片，则过滤掉
      if (uploadedList.length > 0) {
        inTaskArrItem.allChunkList = inTaskArrItem.allChunkList.filter(
          (item: any) => !uploadedList.includes(item.chunkHash)
        );

        if (!inTaskArrItem.allChunkList.length) {
          await handleMrege(inTaskArrItem);
          return false;
        } else {
          inTaskArrItem.allChunkList = inTaskArrItem.allChunkList.map((item: any) => {
            return {
              ...item,
              chunkNumber: item.allChunkList.length,
            };
          });
        }
      }

      uploadSignleFile(inTaskArrItem);
    }
  } catch (error) {
    console.log('上传失败', error);
  }
};
// 单个文件上传
export const uploadSignleFile = (taskArrItem: any) => {
  // 如果没有需要上传的切片 / 正在上传的切片还没传完，就不做处理
  if (taskArrItem.allChunkList.length === 0 || taskArrItem.whileRequests.length > 0) {
    return false;
  }
  // 找到文件处于处理中/上传中的 文件列表（是文件而不是切片）
  const isTaskArrIng = uploadFileList.value.filter(
    (itemB: any) => itemB.state === 1 || itemB.state === 2
  );

  // 实时动态获取并发请求数,每次调请求前都获取一次最大并发数
  // 浏览器同域名同一时间请求的最大并发数限制为6
  // 例如如果有3个文件同时上传/处理中，则每个文件切片接口最多调 6 / 3 == 2个相同的接口
  maxRequest.value = Math.ceil(6 / isTaskArrIng.length);

  // 从数组的末尾开始提取 maxRequest 个元素。
  let whileRequest = taskArrItem.allChunkList.slice(-maxRequest.value);

  // 设置正在请求中的个数
  taskArrItem.whileRequests.push(...whileRequest);
  //  如果总请求数大于并发数
  if (taskArrItem.allChunkList.length > maxRequest.value) {
    // 则减去并发数
    taskArrItem.allChunkList.splice(-maxRequest.value);
  } else {
    // 否则总请求数置空,说明已经把没请求的全部放进请求列表了，不需要做过多请求
    taskArrItem.allChunkList = [];
  }

  // 单个分片请求
  const uploadChunk = async (needObj: any) => {
    const fd = new FormData();
    const {
      fileHash,
      fileSize,
      fileName,
      chunkIndex,
      chunkFile,
      chunkHash,
      chunkSize,
      chunkNumber,
    } = needObj;

    fd.append('fileHash', fileHash);
    fd.append('fileSize', String(fileSize));
    fd.append('fileName', fileName);
    fd.append('index', String(chunkIndex));
    fd.append('chunkFile', chunkFile);
    fd.append('chunkHash', chunkHash);
    fd.append('chunkSize', String(chunkSize));
    fd.append('chunkNumber', String(chunkNumber));
    const res = await uploadFile(fd, (onCancelFunc: Function) => {
      // 在调用接口的同时，相当于同时调用了传入的这个函数，又能同时拿到返回的取消方法去赋值
      needObj.cancel = onCancelFunc;
    });

    console.log('needObj', res.status);
    // 先判断是不是处于暂停还是取消状态
    // 你的状态都已经变成暂停或者中断了,就什么都不要再做了,及时停止
    if (taskArrItem.state === 3 || taskArrItem.state === 5) {
      return false;
    }

    // 请求异常,或者请求成功服务端返回报错都按单片上传失败逻辑处理,.then.catch的.catch是只能捕捉请求异常的
    if (!res || res.status === 500) {
      taskArrItem.errNumber++;
      // 超过3次之后直接上传中断
      if (taskArrItem.errNumber > 3) {
        console.log('切片上传失败超过三次了');
        pauseUpload(taskArrItem, false); // 上传中断
      } else {
        console.log('切片上传失败还没超过3次');
        uploadChunk(needObj); // 失败了一片,继续当前分片请求
      }
    } else if (res.status === 200) {
      // 单个文件上传失败次数大于0则要减少一个
      taskArrItem.errNumber > 0 ? taskArrItem.errNumber-- : 0;
      // 单个文件切片上传成功数+1
      taskArrItem.finishNumber++;
      // 单个切片上传完成
      needObj.finish = true;
      signleFileProgress(needObj, taskArrItem); // 更新进度条
      // 上传成功了就删掉请求中数组中的那一片请求
      taskArrItem.whileRequests = taskArrItem.whileRequests.filter(
        (item: any) => item.chunkFile !== needObj.chunkFile
      );

      // 如果单个文件最终成功数等于切片个数
      if (taskArrItem.finishNumber === chunkNumber) {
        // 全部上传完切片后就开始合并切片
        handleMrege(taskArrItem);
      } else {
        // 如果还没完全上传完，则继续上传
        uploadSignleFile(taskArrItem);
      }
    }
  };

  // 开始单个上传
  for (const item of whileRequest) {
    uploadChunk(item);
  }
};
// 生成文件hash(web-worker)
function useWorker(file: any) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(
      new URL('@/worker/hash-worker.js', import.meta.url)
      // {
      //   type: 'module',
      // }
    );
    worker.postMessage(JSON.stringify({ file, chunkSize: chunkSize }));
    worker.onmessage = (e: any) => {
      const { fileHash, fileChunkList } = e.data;
      if (fileHash) resolve({ fileHash, fileChunkList });
    };
  });
}

// 调取合并接口处理所有切片
async function handleMrege(taskItem: any) {
  const { fileHash, fileName } = taskItem;
  const res = await mergeChunk({
    chunkSize: chunkSize,
    fileName,
    fileHash,
  }).catch(() => {});

  if (res && res.status === 200) {
    // 设置文件上传状态
    finishTask(taskItem);
    console.log('文件合并成功！');
  } else {
    // 否则暂停上传该文件
    pauseUpload(taskItem, true);
    console.log('文件合并失败！');
  }

  // 赋值切片文件完成个数为0
  taskItem.finishNumber = 0;
}

// 更新单个文件进度条
function signleFileProgress(needObj: any, taskItem: any) {
  taskItem.percentage = Math.floor((needObj.finishNumber / needObj.chunkNumber) * 100).toFixed(2);
}

// 上传单个文件已完成
function finishTask(taskItem: any) {
  taskItem.state = 4;
  taskItem.percentage = 100;
}

// 暂停上传（是暂停剩下未上传的）
export const pauseUpload = (taskArrItem: any, elsePause = true) => {
  // elsePause为true就是主动暂停，为false就是请求中断
  // 4是成功 6是失败  如果不是成功或者失败状态，
  if (![4, 6].includes(taskArrItem.state)) {
    // 3是暂停，5是中断
    if (elsePause) {
      taskArrItem.state = 3;
    } else {
      taskArrItem.state = 5;
    }
  }
  taskArrItem.errNumber = 0;

  // 取消还在请求中的所有接口
  if (taskArrItem.whileRequests.length > 0) {
    for (const itemB of taskArrItem.whileRequests) {
      itemB.cancel ? itemB.cancel() : '';
    }
  }
};

// 继续上传
export const resumeUpload = (taskArrItem: any) => {
  // 2为上传中
  taskArrItem.state = 2;
  // 把刚才暂停的正在上传中所有切片放到待上传切片列表中
  taskArrItem.allChunkList.push(...taskArrItem.whileRequests);
  taskArrItem.whileRequests = [];
  uploadSignleFile(taskArrItem);
};

// 取消单个
export const cancelSingle = async (taskArrItem: any) => {
  pauseUpload(taskArrItem);
  // 取消上传后列表删除该文件
  uploadFileList.value = uploadFileList.value.filter(
    (itemB: any) => itemB.fileHash !== taskArrItem.fileHash
  );
};

// 全部取消
export const cancelAll = () => {
  for (const item of uploadFileList.value) {
    pauseUpload(item);
  }

  uploadFileList.value = [];
};
